package jbase.project.system.net.http;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

import jbase.project.system.core.Sys;
import jbase.project.system.core.common.Console;
import jbase.project.system.core.common.EventHook;
import jbase.project.system.exception.SysException;
import jbase.project.system.io.EndIdentificationBlock;
import jbase.project.system.io.IOTransfer;
import jbase.project.system.util.HttpUtil;

public class HttpProxyNetService implements HttpConstants, Console {

	private InputStream httpIn = null;
	private OutputStream httpOut = null;
	private Map<String, Object> config = null;
	private EventHook hook;

	protected final static String CONNECT_SUCCESS_RESPONSE_HEADERS = "HTTP/1.1 200 Connection Established\r\n\r\n";
	protected final static String CONNECT_502_CODE_RESPONSE_HEADERS = "HTTP/1.1 502 Bad Gateway\r\n\r\n";

	protected final static int STATUS_READ_REQUEST_HEADER = 0x1;
	protected final static int STATUS_READ_REQUEST_BODY = 0x2;
	protected final static int STATUS_READ_REQUEST_BODY_READ_CONTENT = 0x3;
	protected final static int STATUS_READ_REQUEST_BODY_RD_TRUNKED_HEADER = 0x4;
	protected final static int STATUS_READ_REQUEST_BODY_RD_TRUNKED_BODY = 0x6;
	protected final static int STATUS_READ_REQUEST_BODY_TD_TRUNKED_TRAILER = 7;
	protected final static int STATUS_OVER = 0x5;

	private Map<String, Object> CONNECT_CHANNELS = new HashMap<String, Object>();

	public HttpProxyNetService(EventHook hook, InputStream httpIn, OutputStream httpOut, Map<String, Object> config) {
		this.hook = hook;
		this.httpIn = httpIn;
		this.httpOut = httpOut;
		config = config == null ? new HashMap<String, Object>() : config;
		this.config = config;
		hook.event("created", this);
	}

	public Map<String, Object> doService() {

		Map<String, Object> result = new HashMap<String, Object>();

		int inCnt = 0;
		byte[] inBuffer = new byte[64 * 1024];

		ByteArrayOutputStream headersData = null;
		ByteArrayOutputStream bodyData = null;

		String headers = null;
		String host = null;
		String method = null;
		String methodsPath = null;
		int contentLength = -1;
		String ver = null;
		String proxyConnection = null;
		String connection = null;

		EndIdentificationBlock headerBlock = new EndIdentificationBlock(DBCRLF);

		int current_status = STATUS_READ_REQUEST_HEADER;
		int transfer_status = 0x1;
		int bodyContentRdCnt = 0;

		boolean IS_CONTINUE_READ = true;
		try {
			while (IS_CONTINUE_READ) {
				// System.out.println("--一级循环--");
				/**
				 * 从HttpAgent读取数据-------------------
				 */
				while ((inCnt = httpIn.read(inBuffer)) != -1) {
					for (int i = 0; i < inCnt; i++) {
						byte currentByte = inBuffer[i];
						switch (current_status) {
						case STATUS_READ_REQUEST_HEADER:
							headerBlock.put(currentByte);
							if (headerBlock.haveComplete()) {

								headersData = headerBlock.getByteArrayOutputStream();
								headerBlock = new EndIdentificationBlock(DBCRLF);

								headers = new String(headersData.toByteArray());
								hook.event("out", "Header-->>>---------\n" + headers + "\n");
								methodsPath = HttpUtil.getRequestMethodsPath(headers);
								method = HttpUtil.getRequestMethod(headers);
								ver = HttpUtil.getRequestVer(headers);
								host = HttpUtil.getRequestHost(headers);
								contentLength = HttpUtil.getContentLength(headers);
								proxyConnection = HttpUtil.getProxyConnection(headers);
								connection = HttpUtil.getConnection(headers);

								Sys.setThreadVar("request.host", host);
								Sys.setThreadVar("request.headers", headers);
								Sys.setThreadVar("request.methodPath", methodsPath);
								Sys.setThreadVar("request.method", method);
								Sys.setThreadVar("request.version", ver);
								Sys.setThreadVar("request.contentLength", contentLength);
								Sys.setThreadVar("request.proxyConnection", proxyConnection);
								Sys.setThreadVar("request.connection", connection);

								// 未完善，需要根据Keep-Alive来判断是继续下一个生命周期
								if (method.equals(HTTP_GET)) {
									service(headersData, bodyData, headers, methodsPath, method, ver, host,
											contentLength);
									current_status = STATUS_READ_REQUEST_HEADER;
								} else if (method.equals(HTTP_POST)) {
									if (contentLength > -1) {
										current_status = STATUS_READ_REQUEST_BODY;
										transfer_status = STATUS_READ_REQUEST_BODY_READ_CONTENT;
										bodyData = new ByteArrayOutputStream();
										bodyContentRdCnt = 0;
									}
								} else if (method.equals(HTTP_PUT)) {

								} else if (method.equals(HTTP_DELETE)) {

								} else if (method.equals(HTTP_CONNECT)) {
									service(headersData, bodyData, headers, methodsPath, method, ver, host,
											contentLength);
									current_status = STATUS_READ_REQUEST_HEADER;
								}
							}

							break;
						case STATUS_READ_REQUEST_BODY:
							switch (transfer_status) {
							case STATUS_READ_REQUEST_BODY_READ_CONTENT:
								bodyContentRdCnt++;
								bodyData.write(new byte[] { currentByte });
								if (bodyContentRdCnt == contentLength) {
									service(headersData, bodyData, headers, methodsPath, method, ver, host,
											contentLength);
									current_status = STATUS_READ_REQUEST_HEADER;
								}
								break;
							}
							break;
						case STATUS_OVER:
							IS_CONTINUE_READ = false;
							break;
						default:

							break;
						}
					}
				}
				IS_CONTINUE_READ = false;
			}
		} catch (IOException | SysException e) {
			try {
				this.httpIn.close();
			} catch (Exception ex) {

			}
			try {
				this.httpOut.close();
			} catch (Exception ex) {

			}
			hook.event("io.exception", e);
		}
		return result;
	}

	private void service(ByteArrayOutputStream headersData, ByteArrayOutputStream bodyData, String headers,
			String methodPath, String method, String version, String host, int contentLength) throws SysException {

		if (method.equals(HTTP_GET)) {
			// 代理服务器设置------------------------------------------

			String relativeLocation = HttpUtil.getRequestRelativeMethodLocation(headers);
			HttpRequestHeaders requestHeaders = new HttpRequestHeaders(headers);
			requestHeaders.setLine(0, method + " " + relativeLocation + " " + version);
			requestHeaders.removeProxyConnection();

			HttpConnectChannel hostChannel = findChannel(host);
			if (hostChannel != null && hostChannel.isAlive) {
				try {
					hostChannel.writeData(requestHeaders.toString().getBytes());
				} catch (SysException e) {
					e.printStackTrace();
					try {
						hostChannel = newChannel(host);
					} catch (SysException e1) {
						e1.printStackTrace();
						throw new SysException(e);
					}
				}
				try {
					byte[] res = hostChannel.doReadResponse();
					if (res.length < 5) {
						throw new RuntimeException();
					}
					this.httpOut.write(res);
					this.httpOut.flush();
				} catch (SysException | IOException e) {
					e.printStackTrace();
					throw new SysException(e);
				}
			} else {
				try {
					byte[] response502 = CONNECT_502_CODE_RESPONSE_HEADERS.getBytes("ISO8859-1");
					this.httpOut.write(response502);
					this.httpOut.flush();
				} catch (IOException e) {
				}
			}

		} else if (method.equals(HTTP_POST)) {
			// 代理服务器设置------------------------------------------

			String relativeLocation = HttpUtil.getRequestRelativeMethodLocation(headers);
			HttpRequestHeaders requestHeaders = new HttpRequestHeaders(headers);
			requestHeaders.setLine(0, method + " " + relativeLocation + " " + version);
			requestHeaders.removeProxyConnection();

			HttpConnectChannel hostChannel = findChannel(host);
			if (hostChannel != null && hostChannel.isAlive) {
				try {
					hostChannel.writeData(requestHeaders.toString().getBytes());
					hostChannel.writeData(bodyData.toByteArray());
				} catch (SysException e) {
					try {
						hostChannel = newChannel(host);
					} catch (SysException e1) {
						e1.printStackTrace();
						throw new SysException(e1);
					}
				}
				try {
					byte[] resp = hostChannel.doReadResponse();
					if (resp.length < 5) {
						throw new RuntimeException();
					}
					this.httpOut.write(resp);
					this.httpOut.flush();
				} catch (SysException | IOException e) {
					e.printStackTrace();
					throw new SysException(e);
				}
			} else {
				try {
					byte[] response502 = CONNECT_502_CODE_RESPONSE_HEADERS.getBytes("ISO8859-1");
					this.httpOut.write(response502);
					this.httpOut.flush();
				} catch (IOException e) {
				}
			}

		} else if (method.equals(HTTP_CONNECT)) {
			HttpConnectChannel channel = findChannel(host);
			if (channel != null) {
				try {
					byte[] responseSuccessTxt = CONNECT_SUCCESS_RESPONSE_HEADERS.getBytes("ISO8859-1");
					this.httpOut.write(responseSuccessTxt);
					this.httpOut.flush();
				} catch (IOException e) {
					throw new SysException(e);
				}
			} else {
				try {
					byte[] response502 = CONNECT_502_CODE_RESPONSE_HEADERS.getBytes("ISO8859-1");
					this.httpOut.write(response502);
					this.httpOut.flush();
				} catch (IOException e1) {
				}
				try {
					this.httpIn.close();
				} catch (IOException e) {
				}
				try {
					this.httpOut.close();
				} catch (IOException e) {
				}
				return;
			}
			class Handler implements EventHook {
				public Object event(String event, Object... objects) {
					if (event.equals("io.exception") || event.equals("io.exit")) {
						try {
							httpIn.close();
						} catch (IOException e) {
						}
						try {
							httpOut.close();
						} catch (IOException e) {
						}
					}
					return true;
				}
			}

			EventHook handler = new Handler();

			IOTransfer up = new IOTransfer(handler, this.httpIn, channel.getOutput());
			IOTransfer down = new IOTransfer(handler, channel.getInput(), this.httpOut);

			Thread upThread = up.createAndStart();
			Thread downThread = down.createAndStart();

			try {
				upThread.join();
			} catch (Exception e) {
				e.printStackTrace();
			}
			try {
				downThread.join();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	private HttpConnectChannel findChannel(String host) throws SysException {

		HttpConnectChannel hostChannel = (HttpConnectChannel) CONNECT_CHANNELS.get(host);

		if (hostChannel == null) {
			try {
				hostChannel = new HttpConnectChannel(this.hook, host);
			} catch (SysException e) {
				this.hook.event("exception", e);
				return null;
			}
			CONNECT_CHANNELS.put(host, hostChannel);
		}
		if (!hostChannel.isAlive) {
			hostChannel.close();
			try {
				hostChannel = new HttpConnectChannel(this.hook, host);
			} catch (SysException e) {
				this.hook.event("exception", e);
				return null;
			}
			CONNECT_CHANNELS.put(host, hostChannel);
		}
		return hostChannel;
	}

	private HttpConnectChannel newChannel(String host) throws SysException {
		try {
			CONNECT_CHANNELS.put(host, new HttpConnectChannel(this.hook, host));
		} catch (SysException e) {
			this.hook.event("exception", e);
			throw new SysException(e);
		}
		return findChannel(host);
	}

	@Override
	public <T, R> R executeCommand(String command, T... objects) {
		return null;
	}

}
